package com.zy.explore.db.infrastructure.util;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.mongodb.client.result.UpdateResult;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.ss.formula.functions.T;
import org.bson.types.ObjectId;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.data.domain.Sort;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.mapreduce.MapReduceResults;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Map;

/**
 * <p>建类说明：MongoDao操作基类</p>
 *
 * @author 中国印
 * @date 2022年02月14日 13:10:45
 */
@Slf4j
@Component
@SuppressWarnings({"unused", "rawtypes", "unchecked"})
public class MongoUtil implements ApplicationContextAware {

    public static final String ID_FIELD = "_id";

    private static MongoTemplate mongoTemplate;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        mongoTemplate = applicationContext.getBean(MongoTemplate.class);
    }

    private MongoUtil() {}

    /**
     * <p>方法说明：通过ID查询对象</p>
     *
     * @param id          id
     * @param clazz 返回类
     * @return {@link T }
     */
    public static Object findById(String id, Class clazz) {
        return mongoTemplate.findById(new ObjectId(id), clazz);
    }

    /**
     * <p>方法说明：查询单个对象</p>
     *
     * @param key         关键
     * @param value       价值
     * @param clazz 返回类
     * @return {@link Object }
     */
    public static Object findOne(String key, String value, Class clazz) {
        Query query = new Query(Criteria.where(key).is(value));
        return mongoTemplate.findOne(query, clazz);
    }

    /**
     * <p>方法说明：通过map查询一个对象</p>
     *
     * @param map         映射集
     * @param clazz 返回类
     * @return {@link Object }
     */
    public static Object findOneByMap(Map<String, Object> map, Class clazz) {
        Query query = new Query();
        for (String key : map.keySet()) {
            query.addCriteria(Criteria.where(key).is(map.get(key)));
        }
        return mongoTemplate.findOne(query, clazz);
    }

    /**
     * <p>方法说明：通过key-value对查询集合</p>
     *
     * @param key         键
     * @param value       值
     * @param clazz 返回类
     * @return {@link List<Object> }
     */
    public static List<Object> findListByKeyValue(String key, Object value, Class clazz) {
        Query query = new Query(Criteria.where(key).is(value));
        return mongoTemplate.find(query, clazz);
    }

    /**
     * <p>方法说明：通过key-values查询集合</p>
     *
     * @param key         键
     * @param values      值集合
     * @param clazz 返回类
     * @return {@link List<Object> }
     */
    public static List<Object> findListByKeyValues(String key, Collection<Object> values, Class clazz) {
        Query query = new Query(Criteria.where(key).in(values));
        return mongoTemplate.find(query, clazz);
    }

    /**
     * <p>方法说明：通过key-value对查询集合</p>
     *
     * @param key         键
     * @param value       值
     * @param limit       限制数量
     * @param clazz 返回类
     * @return {@link List<Object> }
     */
    public static List<Object> findListByKeyValue(String key, Object value, int limit, Class clazz) {
        Query query = new Query(Criteria.where(key).is(value));
        query.limit(limit);
        return mongoTemplate.find(query, clazz);
    }

    /**
     * <p>方法说明：通过key-values查询集合</p>
     *
     * @param key         键
     * @param values      值
     * @param limit       限制数量
     * @param clazz       返回类
     * @return {@link List<Object> }
     */
    public static List<Object> findListByKeyValues(String key, Collection<Object> values, int limit, Class clazz) {
        Query query = new Query(Criteria.where(key).in(values));
        query.limit(limit);
        return mongoTemplate.find(query, clazz);
    }

    public static List<Object> findListByIds(Collection<String> ids, Class clazz) {
        Query query = new Query(Criteria.where(ID_FIELD).in(ids));
        return mongoTemplate.find(query, clazz);
    }

    /**
     * <p>方法说明：查询全部集合</p>
     *
     * @param clazz 返回类
     * @return {@link List<Object> }
     */
    public static List<Object> findAll(Class clazz) {
        return mongoTemplate.findAll(clazz);
    }

    /**
     * <p>方法说明：查询总数</p>
     *
     * @param clazz 返回类
     * @return long
     */
    public static long getCount(Class clazz) {
        return mongoTemplate.count(new Query(), clazz);
    }

    /**
     * <p>方法说明：根据key-value查询数量</p>
     *
     * @param key         键
     * @param value       值
     * @param clazz 返回类
     * @return long
     */
    public static long getCount(String key, String value, Class clazz) {
        Query query = new Query(Criteria.where(key).is(value));
        return mongoTemplate.count(query, clazz);
    }

    /**
     * <p>方法说明：保存对象</p>
     *
     * @param obj 对象
     * @return {@link Object }
     */
    public static Object save(Object obj) {
        return obj == null ? null : mongoTemplate.save(obj);
    }

    /**
     * <p>方法说明：保存对象集合</p>
     *
     * @param collection 集合
     * @return int 返回保存的数量
     */
    public static int save(Collection<Object> collection) {
        return CollectionUtils.isEmpty(collection) ? 0 : mongoTemplate.insertAll(collection).size();
    }

    /**
     * <p>方法说明：根据key-value更新对象</p>
     *
     * @param key         键
     * @param value       值
     * @param obj         更新对象
     * @return long       返回更新的数量
     */
    public static long updateByKeyValue(String key, String value, Object obj) {
        try {
            Query query = new Query(Criteria.where(key).is(value));
            Update update = new Update();
            Field[] fields = obj.getClass().getDeclaredFields();
            for (Field f : fields) {
                f.setAccessible(true);
                if ("serialVersionUID".equals(f.getName())) {
                    continue;
                }
                if (f.get(obj) != null) {
                    update.set(f.getName(), f.get(obj));
                }
            }
            return mongoTemplate.updateFirst(query, update, obj.getClass()).getModifiedCount();
        } catch (Exception e) {
            return 0L;
        }
    }

    /**
     * <p>方法说明：根据ID更新单个对象</p>
     *
     * @param id          id
     * @param update      更新参数对象
     * @param clazz 更新类
     * @return long       返回更新的数量
     */
    public static long updateById(String id, Update update, Class clazz) {
        Query query = new Query(Criteria.where(ID_FIELD).is(id));
        return (int) mongoTemplate.updateFirst(query, update, clazz).getModifiedCount();
    }

    /**
     * <p>方法说明：根据ID集合更新多个对象</p>
     *
     * @param ids         id集合
     * @param update      更新参数对象
     * @param clazz       更新类
     * @return int
     */
    public static long updateByIds(Collection<String> ids, Update update, Class clazz) {
        Query query = new Query(Criteria.where(ID_FIELD).in(ids));
        return mongoTemplate.updateMulti(query, update, clazz).getModifiedCount();
    }

    /**
     * <p>方法说明：通过映射集更新</p>
     *
     * @param query       查询
     * @param map         映射集
     * @param clazz       更新类
     * @return int
     */
    public static long updateByMap(Query query, Map<String, Object> map, Class clazz) {
        Update update = new Update();
        for (String key : map.keySet()) {
            if (map.get(key) != null) {
                update.set(key, map.get(key));
            }
        }
        return mongoTemplate.updateMulti(query, update, clazz).getModifiedCount();
    }

    /**
     * <p>方法说明：通过查询对象删除</p>
     *
     * @param query 查询对象
     * @param clazz clazz
     * @return long 返回删除的数量
     */
    public static long remove(Query query, Class clazz) {
        return mongoTemplate.remove(query, clazz).getDeletedCount();
    }

    /**
     * <p>方法说明：通过ID删除对象</p>
     *
     * @param id    id
     * @param clazz 要删除的类型
     * @return      返回删除的数量
     */
    public static Long remove(String id, Class clazz) {
        return mongoTemplate.remove(new Query(Criteria.where(ID_FIELD).is(id)), clazz).getDeletedCount();
    }

    /**
     * <p>方法说明：删除对象</p>
     *
     * @param o 要删除的对象
     * @return  返回删除的数量
     */
    public static long remove(Object o) {
        return mongoTemplate.remove(o).getDeletedCount();
    }

    /**
     * <p>方法说明：根据ID集合批量删除</p>
     *
     * @param ids   id集合
     * @param clazz 要删除的类
     * @return long 返回删除的数量
     */
    public static long remove(Collection<String> ids, Class clazz) {
        return mongoTemplate.remove(new Query(Criteria.where(ID_FIELD).in(ids)), clazz).getDeletedCount();
    }

    /**
     * <p>方法说明：map-reduce</p>
     *
     * @param query               查询
     * @param inputCollectionName 输入集合名称
     * @param mapFunction         map函数
     * @param reduceFunction      reduce函数
     * @param clazz               clazz
     * @return {@link MapReduceResults<O> }
     */
    public static <O> MapReduceResults<O> mapReduce(Query query, String inputCollectionName, String mapFunction, String reduceFunction, Class<O> clazz) {
        return mongoTemplate.mapReduce(query, inputCollectionName, mapFunction, reduceFunction, clazz);
    }

    /**
     * <p>方法说明：获取对象在mongo中的存储集合名称</p>
     *
     * @param clazz 实体类字节码
     * @return {@link String }
     */
    public static String getCollectionName(Class<?> clazz) {
        return mongoTemplate.getCollectionName(clazz);
    }

    /**
     * <p>方法说明：根据查询对象查询并删除</p>
     *
     * @param query 查询对象
     * @param clazz 要删除的对象
     * @return {@link Object } 返回删除的对象
     */
    public static Object findAndRemove(Query query, Class clazz) {
        return mongoTemplate.findAndRemove(query, clazz);
    }

    /**
     * <p>方法说明：根据查询对象查找和更新对象</p>
     *
     * @param query  查询对象
     * @param update 更新对象
     * @param clazz  要更新的类字节码
     * @return {@link Object } 返回更新后的类
     */
    public static Object findAndUpdate(Query query, Update update, Class clazz) {
        return mongoTemplate.findAndModify(query, update, clazz);
    }

    /**
     * <p>方法说明：发现子</p>
     *
     * @param id       id
     * @param clazz    clazz
     * @param subClazz 子clazz
     * @return {@link Object }
     */
    public static Object findSub(String id, Class clazz, Class subClazz) {
        String subName = getCollectionName(subClazz);
        Query query = Query.query(Criteria.where(ID_FIELD).is(id));
        Object entity = mongoTemplate.findOne(query, clazz);
        JSONObject object = JSONArray.parseObject(JSON.toJSONString(entity));
        return object.getObject(subName, subClazz.getClass());
    }

    /**
     * <p>方法说明：查询子类属性</p>
     *
     * @param id       id
     * @param clazz    父类字节码
     * @param subClazz 子类字节码
     * @param subName  子的名字
     * @return {@link Object }
     */
    public static Object findSub(String id, Class clazz, Class subClazz, String subName) {
        Query query = Query.query(Criteria.where(ID_FIELD).is(id));
        Object entity = mongoTemplate.findOne(query, clazz);
        JSONObject object = JSONArray.parseObject(JSON.toJSONString(entity));
        return object.getObject(subName, subClazz.getClass());
    }

    public static List findList(Query query, Class clazz) {
        return mongoTemplate.find(query, clazz);
    }

    public static List<Object> findList(Query query, Class clazz, Sort... sort) {
        if (sort != null) {
            for (Sort s : sort) {
                if (s != null) {
                    query.with(s);
                }
            }
        }
        return mongoTemplate.find(query, clazz);
    }

    public static List<Object> findList(Query query, Class clazz, int limit) {
        query.limit(limit);
        return mongoTemplate.find(query, clazz);
    }

    public static List<Object> findListByMap(Map<String, Object> queryMap, Class clazz) {
        if (queryMap.isEmpty()) {
            return new ArrayList<>(0);
        }
        Query query = new Query();
        for (String key : queryMap.keySet()) {
            query.addCriteria(Criteria.where(key).is(queryMap.get(key)));
        }
        return mongoTemplate.find(query, clazz);
    }

    public static List<Object> findListByIds(Collection<String> ids, Class clazz, Sort... sort) {
        Query query = Query.query(Criteria.where(ID_FIELD).in(ids));
        if (sort != null) {
            for (Sort s : sort) {
                if (s != null) {
                    query.with(s);
                }
            }
        }
        return mongoTemplate.find(query, clazz);
    }

    public static Object findOne(Query query, Class clazz) {
        return mongoTemplate.findOne(query, clazz);
    }

    public static Long getCount(Query query, Class clazz) {
        return mongoTemplate.count(query, clazz);
    }

    public static List<String> findField(Query query, Class clazz, String fieldName) {
        List list = mongoTemplate.find(query, clazz);
        List<String> fieldList = new ArrayList<String>();
        if (CollectionUtils.isEmpty(list)) {
            return fieldList;
        }
        JSONArray array = JSONArray.parseArray(JSON.toJSONString(list));
        for (int i = 0; i < array.size(); i++) {
            fieldList.add(array.getJSONObject(i).getString(fieldName));
        }
        return fieldList;
    }

    public static List<String> findId(Query query, Class clazz) {
        return findField(query, clazz, ID_FIELD);
    }

    public static Long getCount(Map<String, Object> queryMap, Class clazz) {
        if (queryMap.isEmpty()) {
            return mongoTemplate.count(new Query(), clazz);
        }
        Query query = new Query();
        for (String key : queryMap.keySet()) {
            query.addCriteria(Criteria.where(key).is(queryMap.get(key)));
        }
        return mongoTemplate.count(query, clazz);
    }

    public static Object insert(Object entity) {
        mongoTemplate.insert(entity);
        return entity;
    }

    /**
     * 这里要注意实体必须要无参的构造函数
     *
     * @param map 映射集
     * @return 返回具体对象
     */
    public static Object insertByMap(Map<String, Object> map, Class clazz) {
        try {
            Object t = clazz.newInstance();
            List<Field> fieldList = new ArrayList<>();
            // 获取父类的字段
            Field[] fieldArr = clazz.getSuperclass().getDeclaredFields();
            // 获取父类的字段
            Field[] ownerFieldArr = clazz.getDeclaredFields();
            fieldList.addAll(Arrays.asList(fieldArr));
            fieldList.addAll(Arrays.asList(ownerFieldArr));
            for (Field field : fieldList) {
                if (map.containsKey(field.getName())) {
                    field.setAccessible(true);
                    field.set(t, map.get(field.getName()));
                }
            }
            mongoTemplate.insert(t);
            return t;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    public static int upsert(Query query, Update update, Class clazz) {
        UpdateResult updateResult = mongoTemplate.upsert(query, update, clazz);
        return (int) updateResult.getModifiedCount();
    }

    public static int update(Query query, Update update, boolean isFirst, Class clazz) {
        UpdateResult updateResult;
        if (isFirst) {
            updateResult = mongoTemplate.updateFirst(query, update, clazz);
        } else {
            updateResult = mongoTemplate.updateMulti(query, update, clazz);
        }
        return (int) updateResult.getModifiedCount();
    }

    /**
     * 实体必须有id字段，id不能为空，更新非空字段
     *
     * @param entity 实体对象
     * @return 返回更新数量
     */
    public static int updateById(T entity) {
        try {
            if (entity == null) {
                return 0;
            }
            Method method = entity.getClass().getMethod("getId");
            String id = (String) method.invoke(entity);
            if (id == null) {
                return 0;
            }
            Query query = new Query(Criteria.where(ID_FIELD).is(id));
            Update update = new Update();
            Field[] fields = entity.getClass().getDeclaredFields();
            for (Field f : fields) {
                f.setAccessible(true);
                if ("serialVersionUID".equals(f.getName())) {
                    continue;
                }
                if (f.get(entity) != null) {
                    update.set(f.getName(), f.get(entity));
                }
            }
            return (int) mongoTemplate.updateFirst(query, update, entity.getClass()).getModifiedCount();
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }

    public static int updateByMap(Map<String, Object> map, T entity) {
        try {
            Query query = new Query();
            for (String key : map.keySet()) {
                query.addCriteria(Criteria.where(key).is(map.get(key)));
            }
            Update update = new Update();
            Field[] fields = entity.getClass().getDeclaredFields();
            for (Field f : fields) {
                f.setAccessible(true);
                if ("serialVersionUID".equals(f.getName())) {
                    continue;
                }
                if (f.get(entity) != null) {
                    update.set(f.getName(), f.get(entity));
                }
            }
            return (int) mongoTemplate.updateFirst(query, update, entity.getClass()).getModifiedCount();
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }

    public static void update(Object entity, Class clazz) {
        Class<?> cls = entity.getClass();
        Field[] fields = cls.getDeclaredFields();
        String id = null;
        Update update = new Update();
        try {
            for (Field f : fields) {
                f.setAccessible(true);
                if ("id".equals(f.getName())) {
                    id = f.get(entity).toString();
                } else if (f.get(entity) != null) {
                    update.set(f.getName(), f.get(entity));
                }
            }
        } catch (IllegalArgumentException | IllegalAccessException e) {
            e.printStackTrace();
        }
        if (id == null) {
            return;
        }
        System.out.println("id:" + id + " update:" + update);
        updateById(id, update, clazz);
    }
}
